#!/usr/bin/env python3
import fnmatch
from os import path, environ
import requests
import sys
import subprocess

CIRCLE_JOB_STATUS_SUCCESS = 'success'

'''
Returns a tuple of status & gitsha of the PR that triggered the build.
status is True if all jobs of the last build were in 'success' state
otherwise False

Each push to a branch triggers a new CI run, and any previous run is cancelled.
A CI run starts 4 jobs (see JOBS above) and each job has a unique "workflow ID"
attached to it. This workflow ID changes for every CI run. In order to get the
status of the last run, we do the following:
- Get status of last 8 jobs from the PR branch - 4 jobs will be from the
  current run and 4 from the previous run
- Ignore jobs of the current workflow (using workflow ID)
- The last run is successful if ALL jobs had a status of 'success'
- Store the gitsha of the last successful run. This is later used to fetch the
  list of files changed since last CI run
'''


def last_build_successful():
    branch = environ.get('CIRCLE_BRANCH')
    current_workflow_id = environ.get('CIRCLE_WORKFLOW_ID')
    token = environ.get('CIRCLE_API_TOKEN')

    if not branch:
        print('CIRCLE_BRANCH env not set! Unable to do CI skip check', file=sys.stderr)
        return False, None
    if not current_workflow_id:
        print('CIRCLE_WORKFLOW_ID env not set! Unable to do CI skip check', file=sys.stderr)
        return False, None
    if not token:
        print('CIRCLE_API_TOKEN env not set! Unable to do CI skip check', file=sys.stderr)
        return False, None

    # jobs defined in .circleci/config.yml
    JOBS = ['lint-build-debug', 'build-release', 'build-osx']

    # Get twice the number of jobs configured. There would be len(JOBS) from this
    # run, while the rest will be from the last run
    cci_api_url = f'https://circleci.com/api/v1.1/project/github/valhalla/valhalla/tree/{branch}?circle-token={token}&shallow=true&limit={len(JOBS)*2}'

    resp = requests.get(cci_api_url)
    resp.raise_for_status()

    builds = resp.json()
    # print(builds, file=sys.stderr)

    # this should never happen. since this script is run from within a CI run
    # there should be at least 1 entry (i.e this run)
    if not builds:
        print('ERROR! No previous builds found! Not skipping build', file=sys.stderr)
        return False, None

    # build a dict of {job_name: (job_status, gitsha)}
    job_status = {}
    for build in builds:
        # ignore this build's status. All jobs in a build have the same
        # workflow ID
        if build['workflows']['workflow_id'] == current_workflow_id:
            continue
        job_status[build['workflows']['job_name']] = (build['status'], build['vcs_revision'])

    if sorted(job_status.keys()) != sorted(JOBS):
        print(
            f'Unexpected job name fetched ({sorted(job_status)}). Expected {sorted(JOBS)}',
            file=sys.stderr,
        )
        return False, None

    # Return False if any of the jobs were not in 'success' state
    for status, gitsha in job_status.values():
        if status != 'success':
            return False, gitsha

    return True, job_status[JOBS[0]][1]


CI_IGNORE_FILE = '.circleci/ciignore'
SKIP_CI = "True"

if not path.isfile(CI_IGNORE_FILE):
    # no CI ignore rules configured, run CI
    print('needs CI: no ciignore file', file=sys.stderr)
    sys.exit(0)

# Check if a previous build of this branch was successful or not
status, last_pr_gitsha = last_build_successful()
print(f'last build successful?, gitsha: {status}, {last_pr_gitsha}', file=sys.stderr)

# if last build was not successful, dont skip
# TODO: even on first build we shouldn't run CI for irrelevant files..
if not status:
    print('needs CI: last build was not successful', file=sys.stderr)
    sys.exit(0)

# if commit does not exist anymore, dont skip
git_type = subprocess.run(
    ['git', 'cat-file', '-t', f'{last_pr_gitsha}'], capture_output=True, text=True
).stdout
if 'commit' not in git_type:
    print('needs CI: last commit doesnt exist anymore', file=sys.stderr)
    sys.exit(0)


# Get all changes since the last successful CI run
GIT_CMD = ['git', 'diff', f'HEAD..{last_pr_gitsha}', '--name-only']

# Get list of changed files staged for commit
changed_files = subprocess.run(GIT_CMD, capture_output=True, text=True).stdout.splitlines()
# Get CI ignore rules
with open(CI_IGNORE_FILE, 'r') as f:
    CI_IGNORE_RULES = f.readlines()
CI_IGNORE_RULES = [x.strip() for x in CI_IGNORE_RULES]

ignored_files = []
for changed_file in changed_files:
    for ignore_rule in CI_IGNORE_RULES:
        if fnmatch.fnmatch(changed_file, ignore_rule):
            ignored_files.append(changed_file)

print(f"changed files: {changed_files}", file=sys.stderr)
print(f"ignored files: {ignored_files}", file=sys.stderr)

if sorted(ignored_files) == sorted(changed_files):
    # everything was ignored, skip CI
    print(
        'skipping CI because the changed stuff and the stuff we want to ignore is the same',
        file=sys.stderr,
    )
    print('skip CI')
else:
    # Not everything was ignored, run CI
    print('needs CI', file=sys.stderr)
